package jmcnet.libcommun.utilit.evaluator;

import java.util.Map;

import org.apache.log4j.Logger;

public class ExpressionEvaluator {

	private static Logger log= Logger.getLogger(ExpressionEvaluator.class);
	
	private ExpressionParser parser = null;
	public ExpressionEvaluator() {
	}
	
	public ExpressionEvaluator(ExpressionParser parser) {
		this.parser = parser;
	}
	
	public ExpressionParser getParser() {
		return parser;
	}

	public void setParser(ExpressionParser parser) {
		this.parser = parser;
	}

	public boolean evaluateBooleanExpression(String booleanExpression, Map<String, Object> variables) throws ExpressionParserException {
		log.debug("Appel evaluateBooleanExpression expression="+booleanExpression+" variables="+variables);
		if (parser == null) {
			throw new ExpressionParserException("Parser not set");
		}
		ParsedExpression pe = parser.parseStringExpression(booleanExpression);
		
		Object res = evaluateBooleanExpression(pe, variables);
		boolean rb = verifierBoolean(res);
		log.debug("The expression '"+booleanExpression+" evaluate to "+rb);
		return rb;
	}
			
	private Object evaluateBooleanExpression(ParsedExpression pe, Map<String, Object> variables) throws ExpressionParserException {
		switch (pe.getType()) {
		case constanteExpression :
			switch (pe.getConstante()) {
				case cst_false :
					return false;
				case cst_true :
					return true;
				case cst_null :
					return "null";
				default :
					throw new ExpressionParserException("Constante inconnue : "+pe.getConstante());
			}
		case variableExpression :
			// On recherche la valeur de la variable
			if (!variables.containsKey(pe.getVariable())) {
				throw new ExpressionParserException("Variable '"+pe.getVariable()+"' inconnue");
			}
			Object val=variables.get(pe.getVariable());
			if (val == null) val = "null";
			return val;
		case operateurExpression :
			Object valGauche = evaluateBooleanExpression(pe.getExpressionGauche(), variables);
			Object valDroite = evaluateBooleanExpression(pe.getExpressionDroite(), variables);
			switch (pe.getExpressionOperateur()) {
				case op_different :
					return ! valGauche.equals(valDroite);
				case op_egal :
					return valGauche.equals(valDroite);
				case op_et :
					boolean vg = verifierBoolean(valGauche);
					boolean vd = verifierBoolean(valDroite);
					return vg && vd; 
				case op_ou :
					vg = verifierBoolean(valGauche);
					vd = verifierBoolean(valDroite);
					return vg || vd; 
				case op_sup :
					double vgd = verifierNumber(valGauche);
					double vdd = verifierNumber(valDroite);
					return vgd > vdd;
				case op_sup_ou_egal :
					vgd = verifierNumber(valGauche);
					vdd = verifierNumber(valDroite);
					return vgd >= vdd;
				case op_inf :
					vgd = verifierNumber(valGauche);
					vdd = verifierNumber(valDroite);
					return vgd < vdd;
				case op_inf_ou_egal :
					vgd = verifierNumber(valGauche);
					vdd = verifierNumber(valDroite);
					return vgd <= vdd;
				default :
					throw new ExpressionParserException("Operateur '"+pe.getExpressionOperateur()+"' inconnu");
			}
		default :
			throw new ExpressionParserException("Type '"+pe.getType()+"' inconnu");
		}
	}
	
	private boolean verifierBoolean(Object obj) throws ExpressionParserException {
		if (obj == null) return false;
		if (obj instanceof Boolean) return ((Boolean) obj).booleanValue();
		if (obj instanceof Integer) return ((Integer) obj).intValue() != 0;
		if (obj instanceof String) return "1".equals((String) obj) || "true".equalsIgnoreCase((String) obj);
		throw new ExpressionParserException("Object '"+obj.toString()+"' cannot evaluate to Boolean");
	}
	
	private double verifierNumber(Object obj) throws ExpressionParserException {
		if (obj == null) throw new ExpressionParserException("Null object '"+obj+"' cannot evaluate to Number");
		if (obj instanceof Integer) return ((Integer) obj).doubleValue();
		if (obj instanceof Float) return ((Float) obj).doubleValue();
		if (obj instanceof Double) return ((Double) obj).doubleValue();
		if (obj instanceof String) {
			// Try to parse it
			try {
				Double d = Double.parseDouble((String) obj);
				return d.doubleValue();
			} catch (NumberFormatException e) {
				throw new ExpressionParserException("String '"+obj+"' cannot evaluate to Number");
			}
		}
		
		throw new ExpressionParserException("Object '"+obj+"' of class "+obj.getClass().getName()+" cannot evaluate to Number");
	}
}
